﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Text;
using ContainerVariations.Common;
using NUnit.Framework;
using StructureMap;
using StructureMap.Configuration;
using StructureMap.Pipeline;

namespace ContainerVariations.StructureMapSamples
{
    /// <summary>
    /// This is one of two test fixtures for StructureMap. This one creates
    /// a container for each test, bypassing the static ObjectFactory class.  
    /// StructureMapObjectFactoryFixture implements the same tests using the 
    /// ObjectFactory class.
    /// </summary>
    /// <remarks>
    /// My original plan was to provide a set of closely parallel exercises
    /// for each container. So I dug through StructureMap to find a way to
    /// manually create a container for each test.  It more closely parallels 
    /// the  configuration workflow of other containers, and it meant that I 
    /// didn't have to worry about resetting the static ObjectFactory 
    /// between tests.
    /// 
    /// It all works fine, but it isn't really very 'StructureMappy'.
    /// The fact is that all of the reference code out there uses 
    /// ObjectFactory.  And when I use StructureMap, I use ObjectFactory
    /// as well. ObjectFactory really needs some representation here.
    /// 
    /// So I've decided to do both.  This fixture 
    /// (StructureMapContainerFixture) creates a container for each test, 
    /// but might seem a bit odd to StructureMap users who are acustomed to 
    /// using ObjectFactory.
    /// 
    /// Look to StructureMapObjectFactoryFixture for the same exercises using
    /// ObjectFactory.
    /// 
    /// You'll find that they're all pretty much the same thing.
    /// </remarks>
    [TestFixture]
    public class StructureMapContainerFixture : TemplateFixture
    {
        [Test]
        public override void Configure_a_single_object_with_container_lifetime()
        {
            var container =
                new Container(x => 
                x.For<IKnob>()
                .LifecycleIs(new SingletonLifecycle())
                .Use<Knob>()
                );

            var component01 = container.GetInstance<IKnob>();
            var component02 = container.GetInstance<IKnob>();

            Assert.That(component01, Is.Not.Null);
            Assert.That(component01, Is.TypeOf(typeof(Knob)));
            Assert.That(component01, Is.EqualTo(component02));

        }

        [Test]
        public override void Configure_a_single_object_with_transient_lifetime()
        {
            var container =
                new Container(x =>
                x.For<IKnob>()
                .Use<Knob>()
                );

            var component01 = container.GetInstance<IKnob>();
            var component02 = container.GetInstance<IKnob>();

            Assert.That(component01, Is.Not.Null);
            Assert.That(component01, Is.TypeOf(typeof(Knob)));
            Assert.That(component01, Is.Not.EqualTo(component02));
        }

        [Test]
        public override void Configure_a_manually_wired_dependency_graph()
        {
            var container = new Container(x =>
            {
                x.For<IKnob>()
                    .Use<Knob>();
                x.For<IDoor>()
                    .Use<Door>()
                    .Ctor<IKnob>();
                x.For<IHouse>()
                    .Use<House>()
                    .Ctor<IDoor>();
            });

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_an_autowired_dependency_graph()
        {
            var container = new Container(x =>
            {
                x.For<IKnob>()
                    .Use<Knob>();
                x.For<IDoor>()
                    .Use<Door>();
                x.For<IHouse>()
                    .Use<House>();
            });

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Xml_configure_an_autowired_dependency_graph()
        {
            ConfigurationParser xmlParser =
                ConfigurationParser.FromFile("StructureMap.config");
            var builder = new PluginGraphBuilder(xmlParser);
            var container = new Container(builder.Build());

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_container_dynamically_supplemented_with_xml()
        {
            ConfigurationParser xmlParser = ConfigurationParser.FromFile("StructureMap.config");
            var builder = new PluginGraphBuilder(xmlParser);
            var container = new Container(builder.Build());

            container.Configure(x =>
                x.For<IAppleContents>()
                    .Use<AppleSeeds>());

            Assert.That(container.GetInstance<Apple>(), Is.TypeOf(typeof(Apple)));
            Assert.That(container.GetInstance<Orange>(), Is.TypeOf(typeof(Orange)));
        }

        [Test]
        public override void Configure_container_dynamically_modified_by_xml()
        {
            var container = new Container(x =>
            {
                x.IncludeConfigurationFromConfigFile = false;
                x.AddConfigurationFromXmlFile("StructureMap.config");
                x.For<Apple>()
                    .Use<Apple>();
                x.For<IAppleContents>()
                    .Use<AppleSeeds>()
                    .Named("default-contents");
            });

            // We have to register the component we intend to replace with a 
            //   name, and use that exact name for the replacement component
            //   in StructureMap.config.
            // Oddly, you do not have to do so if using ObjectFactory.  Compare
            //   with Configure_container_dynamically_modified_by_xml() in
            //   StructureMapObjectFactoryFixture.
            var apple = container.GetInstance<Apple>();
            Assert.That(apple.ToString(), Is.EqualTo("Apple contains: Worm"));
        }

        [Test]
        public override void Configure_an_open_generic()
        {
            var container = new Container(x =>
            {
                x.For(typeof(IFlattener<>))
                    .Use(typeof(PassthruFlattener<>));
                x.For(typeof(IFlattener<Contact>))
                    .Use(typeof(ContactFlattener));
            });

            var contactFlattener = container.GetInstance<IFlattener<Contact>>();
            var addressFlattener = container.GetInstance<IFlattener<Address>>();
            Assert.That(contactFlattener, Is.TypeOf(typeof(ContactFlattener)));
            Assert.That(addressFlattener, Is.TypeOf(typeof(PassthruFlattener<Address>)));
        }

        [Test]
        public override void Configure_a_type_with_a_lambda_or_delegate_dependency()
        {
            string actualValue = null;

            var container = new Container(x =>
                x.For<ITestActor>()
                .Use(c => new TestActor(s => actualValue = s)));

            var actor = container.GetInstance<ITestActor>();
            const string expectedValue = "This is the value we're assigning.";
            actor.Act(expectedValue);

            Assert.That(actualValue, Is.EqualTo(expectedValue));
        }

        [Test]
        public override void Configure_all_classes_of_a_specified_type_in_a_specified_assembly()
        {
            var container = new Container(x => 
                x.Scan(scanner => 
                    { 
                        scanner.Assembly(typeof(IWidget).Assembly);
                        scanner.AddAllTypesOf<IWidget>();
                    })
                );

            IList<IWidget> widgets = container.GetAllInstances<IWidget>();

            foreach (var widget in widgets)
            {
                Console.WriteLine(widget.GetType().Name + ": " + widget.Call());
            }

            Assert.That(widgets.Count, Is.EqualTo(4));
        }

        [Test]
        public override void Configure_a_class_with_multiple_decorators()
        {
            var container = new Container(x =>
            {
                x.For<IDoorUser>().Use<DoorUser>()
                    .Ctor<bool>("mayOpenDoor").Is(true);
                x.For<IKnob>().Use<Knob>();
                x.For<IDoor>().Use<Door>()
                    .EnrichWith((context, y) => new EmailDoor(new SecureDoor(y, context.GetInstance<IDoorUser>())));
                x.For<IHouse>()
                    .Use<House>();
            });

            var house = container.GetInstance<IHouse>();
            Assert.That(house, Is.Not.Null);
            house.OpenDoor();
        }

        [Test]
        public override void Configure_a_class_with_scalar_dependencies_from_configuration_api()
        {
            bool mayOpenDoor = Convert.ToBoolean(ConfigurationManager.AppSettings["mayOpenDoor"]);
            var container = new Container(x =>
                x.For<IDoorUser>().Use<DoorUser>()
                .Ctor<bool>("mayOpenDoor")
                .EqualToAppSetting("mayOpenDoor", "False"));
            var doorUser = container.GetInstance<IDoorUser>();

            Assert.That(mayOpenDoor, Is.True);
            Assert.That(doorUser.MayOpenDoor, Is.EqualTo(mayOpenDoor));
        }

        [Test]
        public override void Container_can_use_an_instance_created_outside_the_container()
        {
            var stringBuilder = new StringBuilder();
            var writer = new StringWriter(stringBuilder);
            var container = new Container(x =>
                {
                    x.For<TextWriter>().Use(writer);
                    x.For<ISender>().Use<TextWriterSender>();
                });
            var sender = container.GetInstance<ISender>();

            const string message = "This is the message to send.";
            sender.Send(message);
            Assert.That(stringBuilder.ToString(), Is.EqualTo(message));
        }

        [Test]
        [Ignore("The StructureMap container is not IDisposable.")]
        public override void Disposable_classes_with_container_lifetime_are_disposed_when_container_is_disposed()
        {
            /*

               It just doesn't work that way with StructureMap.  Check out the 
               comments regarding IDisposable at this blog entry:
               http://codebetter.com/blogs/jeremy.miller/archive/2009/01/21/interception-capabilities-of-structuremap-2-5.aspx

                    "I'm nearly dead set against the type of IDisposable 
               support that Windsor does.  All I ever hear are complaints 
               of people who have massive memory leaks because Windsor 
               inexplicably keeps refererences."
            
                    "Something like AutoFac's "scoped container" maybe.  How 
               about something like this:

               using (IContainer container = ObjectFactory.NewSession())
               {
                   //do stuff
               } // At the closing of the using statement, all transient objects fetched from container are disposed.

            */
        }

        [Test]
        public override void Disposable_classes_with_transient_lifetime_are_disposed_when_container_is_disposed()
        {
            // This now kinda works with 'child' containers.  But it won't 
            //   work with the top level container.
            // See http://codebetter.com/blogs/jeremy.miller/archive/2010/02/10/nested-containers-in-structuremap-2-6-1.aspx
            IKnob knob;
            var parentContainer = new Container(
                x => x.For<IKnob>().Use<Knob>());

            using (IContainer childContainer = parentContainer.GetNestedContainer())
            {
                knob = childContainer.GetInstance<IKnob>();
                Assert.That(knob.IsDisposed, Is.False);
            }
            Assert.That(knob.IsDisposed, Is.True);
        }

        [Test]
        public override void Register_components_with_scalar_dependencies()
        {
            var container = new Container(x =>
                x.For<Answer>()
                    .Use<Answer>()
                    .Ctor<int>("value").Is(42));
            Assert.That(container.GetInstance<Answer>().Value, Is.EqualTo(42));
        }

        [Test]
        public override void Resolve_components_providing_scalar_dependencies()
        {
            var container = new Container(x =>
                x.For<Answer>().Use<Answer>());
            var args = new Dictionary<string, object>();
            args["value"] = 42;
            var answer = container.GetInstance<Answer>(new ExplicitArguments(args));
            Assert.That(answer.Value, Is.EqualTo(42));
        }
    }
}